
/** Namespaced Pushlet functions. */
var PL = {
  NV_P_FORMAT: 'p_format=xml-strict',
  NV_P_MODE: 'p_mode=pull',
  pushletURL: null,
  webRoot: null,//根目录
  sessionId: null,
  STATE_ERROR: -2,
  STATE_ABORT: -1,//异常中止
  STATE_NULL: 1,
  STATE_READY: 2,//开始
  STATE_JOINED: 3,//加入
  STATE_LISTENING: 3,//监听
  state: 1,//状态
  
  /************** START PUBLIC FUNCTIONS  **************/
  
  /** Send heartbeat. 
   heartbeat: function() {
   PL._doRequest('heartbeat');
   },
   */
  /** Join. */
  join: function() {
    PL.sessionId = null;
    
    // Streaming is only supported in Mozilla. E.g. IE does not allow access to responseText on readyState == 3
    PL._doRequest('join', PL.NV_P_FORMAT + '&' + PL.NV_P_MODE);
  },
  
  /** Join, listen and subscribe. */
  joinListen: function(aSubject) {
    PL._setStatus('join-listen ' + aSubject);
    PL.join();
    PL.listen(aSubject);
  },
  
  /** Close pushlet session. */
  leave: function() {
    PL._doRequest('leave');
  },
  
  /** Listen on event channel. */
  listen: function(aSubject) {
    
    // Create event URI for listen
    var query = PL.NV_P_MODE;
    
    // Optional subject to subscribe to
    if (aSubject) {
      query = query + '&p_subject=' + aSubject;
    }
    
    PL._doRequest('listen', query);
  },
  
  /** Publish to subject. */
  publish: function(aSubject, theQueryArgs) {
    
    var query = 'p_subject=' + aSubject;
    if (theQueryArgs) {
      query = query + '&' + theQueryArgs;
    }
    // alert('publish');
    PL._doRequest('publish', query);
  },
  
  
  /************** END PUBLIC FUNCTIONS  **************/
  
  // Cross-browser add event listener to element
  _addEvent: function (elm, evType, callback, useCapture) {
    var obj = PL._getObject(elm);
    if (obj.addEventListener) {
      obj.addEventListener(evType, callback, useCapture);
      return true;
    } else if (obj.attachEvent) {
      var r = obj.attachEvent('on' + evType, callback);
      return r;
    } else {
      obj['on' + evType] = callback;
    }
  },
  
  _doCallback: function(event, cbFunction) {
    // Do specific callback function if provided by client
    if (cbFunction) {
      // Do specific callback like onData(), onJoinAck() etc.
      cbFunction(event);
    } else if (window.onEvent) {
      // general callback onEvent() provided to catch all events
      onEvent(event);
    }
  },
  
  // Do XML HTTP request
  _doRequest: function(anEvent, aQuery) {
    // Check if we are not in any error state
    if (PL.state < 0) {
      PL._setStatus('died (' + PL.state + ')');
      return;
    }
    
    // We may have (async) requests outstanding and thus
    // may have to wait for them to complete and change state.
    var waitForState = false;
    if (anEvent == 'join') {
      // We can only join after initialization
      waitForState = (PL.state < PL.STATE_READY);
    } else if (anEvent == 'leave') {
      PL.state = PL.STATE_READY;
    } else if (anEvent == 'refresh') {
      // We must be in the listening state
      if (PL.state != PL.STATE_LISTENING) {
        return;
      }
    } else if (anEvent == 'listen') {
      // We must have joined before we can listen
      waitForState = (PL.state < PL.STATE_JOINED);
    } else if (anEvent == 'subscribe' || anEvent == 'unsubscribe') {
      // We must be listeing for subscription mgmnt
      waitForState = (PL.state < PL.STATE_LISTENING);
    } else {
      // All other requests require that we have at least joined
      waitForState = (PL.state < PL.STATE_JOINED);
    }
    
    // May have to wait for right state to issue request
    if (waitForState == true) {
      PL._setStatus(anEvent + ' , waiting... state=' + PL.state);
      setTimeout(function() {
        PL._doRequest(anEvent, aQuery);
      }, 100);
      return;
    }
    
    // ASSERTION: PL.state is OK for this request
    
    // Construct base URL for GET
    var url = PL.pushletURL + '?p_event=' + anEvent;
    
    // Optionally attach query string
    if (aQuery) {
      url = url + '&' + aQuery;
    }
    
    // Optionally attach session id
    if (PL.sessionId != null) {
      url = url + '&p_id=' + PL.sessionId;
      if (anEvent == 'p_leave') {
        PL.sessionId = null;
      }
    }
    // PL.debug('_doRequest', url);
    PL._getXML(url, PL._onResponse);
    
    // uncomment to use synchronous XmlHttpRequest
    //var rsp = PL._getXML(url);
    //PL._onResponse(rsp);  */
  },
  
  // Get object reference
  _getObject: function(obj) {
    if (typeof obj == "string") {
      return document.getElementById(obj);
    } else {
      // pass through object reference
      return obj;
    }
  },
  
  _getWebRoot: function() {
    /** Return directory of this relative to document URL. */
    if (PL.webRoot != null) {
      return PL.webRoot;
    }
    //derive the baseDir value by looking for the script tag that loaded this file
    var head = document.getElementsByTagName('head')[0];
    var nodes = head.childNodes;
    for (var i = 0; i < nodes.length; ++i) {
      var src = nodes.item(i).src;
      if (src) {
        var index = src.indexOf("chat/js/ajax-pushlet-client.js");
        if (index >= 0) {
          PL.webRoot = src.substring(0, index);
          break;
        }
      }
    }
    return PL.webRoot;
  },
  
  // Get XML doc from server
  // On response  optional callback fun is called with optional user data.
  _getXML: function(url, callback) {
    
    // Obtain XMLHttpRequest object
    var xmlhttp = new XMLHttpRequest();
    if (!xmlhttp || xmlhttp == null) {
      alert('No browser XMLHttpRequest (AJAX) support');
      return;
    }
    
    // Setup optional async response handling via callback
    var cb = callback;
    var async = false;
    
    if (cb) {
      // Async mode
      async = true;
      xmlhttp.onreadystatechange = function() {
        if (xmlhttp.readyState == 4) {
          if (xmlhttp.status == 200) {
            // Processing statements go here...
            //alert('1---'+xmlhttp.responseXML);
            cb(xmlhttp.responseXML);
          } else {
            var event = new PushletEvent();
            event.put('p_event', 'error')
            event.put('p_reason', 'Problem retrieving XML data:\n' + xmlhttp.statusText);
            PL._onEvent(event)
          }
        }
      };
    }
    // Open URL
    //alert('url'+url);
    xmlhttp.open('get', url, async);
    // Send XML to KW server
    xmlhttp.send(null);
    
    if (!cb) {
      if (xmlhttp.status != 200) {
        var event = new PushletEvent();
        event.put('p_event', 'error')
        event.put('p_reason', 'Problem retrieving XML data:\n' + xmlhttp.statusText);
        PL._onEvent(event)
        return null;
      }
      // Sync mode (no callback)
      //alert(xmlhttp.responseText);
      
      return xmlhttp.responseXML;
    }
  },
  
  
  _init: function () {
    PL._showStatus();
    PL._setStatus('initializing...');

    // IE support
    if (window.ActiveXObject && !window.XMLHttpRequest) {
      window.XMLHttpRequest = function() {
        var msxmls = new Array(
        'Msxml2.XMLHTTP.5.0',
        'Msxml2.XMLHTTP.4.0',
        'Msxml2.XMLHTTP.3.0',
        'Msxml2.XMLHTTP',
        'Microsoft.XMLHTTP');
        for (var i = 0; i < msxmls.length; i++) {
          try {
            return new ActiveXObject(msxmls[i]);
          } catch (e) {
          }
        }
        return null;
      };
    }
    
    // ActiveXObject emulation
    if (!window.ActiveXObject && window.XMLHttpRequest) {
      window.ActiveXObject = function(type) {
        switch (type.toLowerCase()) {
          case 'microsoft.xmlhttp':
          case 'msxml2.xmlhttp':
          case 'msxml2.xmlhttp.3.0':
          case 'msxml2.xmlhttp.4.0':
          case 'msxml2.xmlhttp.5.0':
            return new XMLHttpRequest();
        }
        return null;
      };
    }
    
    PL.pushletURL = PL._getWebRoot() + 'pushlet.srv';
    PL._setStatus('initialized');
    PL.state = PL.STATE_READY;
  },
  
  /** Handle incoming events from server. */
  _onEvent: function (event) {
    // Create a PushletEvent object from the arguments passed in
    // push.arguments is event data coming from the Server
    
    //PL.debug('_onEvent()', event.toString());
    
    // Do action based on event type
    var eventType = event.getEvent();
    //alert('eventType:'+eventType);
    if (eventType == 'data') {
        PL._setStatus('data');
        PL._doCallback(event, window.onData);
    } else if (eventType == 'refresh') {
        if (PL.state < PL.STATE_LISTENING) {
                PL._setStatus('not refreshing state=' + PL.STATE_LISTENING);
        }
        var timeout = event.get('p_wait');
        setTimeout(function () {
                                PL._doRequest('refresh');
                        }, timeout);
        return;
    } else if (eventType == 'error') {
        PL.state = PL.STATE_ERROR;
        PL._setStatus('server error: ' + event.get('p_reason'));
        PL._doCallback(event, window.onError);
    } else if (eventType == 'join-ack') {
        PL.state = PL.STATE_JOINED;
        PL.sessionId = event.get('p_id');
        PL._setStatus('connected');
        PL._doCallback(event, window.onJoinAck);
    } else if (eventType == 'join-listen-ack') {
        PL.state = PL.STATE_LISTENING;
        PL._setStatus('join-listen-ack');
        PL._doCallback(event, window.onJoinListenAck);
    } else if (eventType == 'listen-ack') {
        PL.state = PL.STATE_LISTENING;
        PL._setStatus('listening');
        PL._doCallback(event, window.onListenAck);
    } else if (eventType == 'hb') {
        PL._setStatus('heartbeat');
        PL._doCallback(event, window.onHeartbeat);
    } else if (eventType == 'hb-ack') {
        PL._doCallback(event, window.onHeartbeatAck);
    } else if (eventType == 'leave-ack') {
        PL._setStatus('disconnected');
        PL._doCallback(event, window.onLeaveAck);
    } else if (eventType == 'refresh-ack') {
        PL._doCallback(event, window.onRefreshAck);
    } else if (eventType == 'subscribe-ack') {
        PL._setStatus('subscribed to ' + event.get('p_subject'));
        PL._doCallback(event, window.onSubscribeAck);
    } else if (eventType == 'unsubscribe-ack') {
        PL._setStatus('unsubscribed');
        PL._doCallback(event, window.onUnsubscribeAck);
    } else if (eventType == 'abort') {
        PL.state = PL.STATE_ERROR;
        PL._setStatus('abort');
        PL._doCallback(event, window.onAbort);
    } else if (eventType.match(/nack$/)) {
        PL._setStatus('error response: ' + event.get('p_reason'));
        PL._doCallback(event, window.onNack);
    }
  },
  
  /**  Handle XMLHttpRequest response XML. */
  _onResponse: function(xml) {
    // PL.debug('_onResponse', xml);
    var events = PL._rsp2Events(xml);
    if (events == null) {
      PL._setStatus('null events')
      return;
    }
    
    delete xml;
    
    // PL.debug('_onResponse eventCnt=', events.length);
    // Go through all <event/> elements
    for (i = 0; i < events.length; i++) {
      PL._onEvent(events[i]);
    }
  },
  
  /** Convert XML response to PushletEvent objects. */
  _rsp2Events: function(xml) {
    if (!xml) {
      return null;
    }
    
    // Convert xml doc to array of PushletEvent objects
    var eventElements = xml.documentElement.getElementsByTagName('event');
    var events = new Array(eventElements.length);
    for (i = 0; i < eventElements.length; i++) {
      events[i] = new PushletEvent(eventElements[i]);
    }
    
    return events;
    
  },
  
  statusMsg: 'null',
  statusChanged: false,
  statusChar: '|',
  
  
  _showStatus: function() {
    // To show progress
    if (PL.statusChanged == true) {
      if (PL.statusChar == '|') PL.statusChar = '/';
      else if (PL.statusChar == '/') PL.statusChar = '--';
      else if (PL.statusChar == '--') PL.statusChar = '\\';
      else PL.statusChar = '|';
      PL.statusChanged = false;
    }
    window.defaultStatus = PL.statusMsg;
    window.status = PL.statusMsg + '  ' + PL.statusChar;
    timeout = window.setTimeout('PL._showStatus()', 400);
  },
  
  _setStatus: function(status) {
    PL.statusMsg = "pushlet - " + status;
    PL.statusChanged = true;
  }
}


/* Represents nl.justobjects.pushlet.Event in JS. */
function PushletEvent(xml) {
  // Member variable setup; the assoc array stores the N/V pairs
  this.arr = new Array();
  
  this.getSubject = function() {
    return this.get('p_subject');
  }
  
  this.getEvent = function() {
    return this.get('p_event');
  }
  
  this.put = function(name, value) {
    return this.arr[name] = value;
  }
  
  this.get = function(name) {
    return this.arr[name];
  }
  
  this.toString = function() {
    var res = '';
    for (var i in this.arr) {
      res = res + i + '=' + this.arr[i] + '\n';
    }
    return res;
  }
  
  this.toTable = function() {
    var res = '<table border="1" cellpadding="3">';
    var styleDiv = '<div style="color:black; font-family:monospace; font-size:10pt; white-space:pre;">'
    
    for (var i in this.arr) {
      res = res + '<tr><td bgColor=white>' + styleDiv + i + '</div></td><td bgColor=white>' + styleDiv + this.arr[i] + '</div></td></tr>';
    }
    res += '</table>'
    return res;
  }
  
  // Optional XML element <event name="value" ... />
  if (xml) {
    // Put the attributes in Map
    for (var i = 0; i < xml.attributes.length; i++) {
      this.put(xml.attributes[i].name, xml.attributes[i].value);
    }
  }
}

// Embed pushlet frame in page (OBSOLETE)
function p_embed(thePushletWebRoot) {
  alert('Pushlet: p_embed() is no longer required for AJAX client')
}

// Join the pushlet server
function p_join() {
  PL.join();
}

// Create data event channel with the server
function p_listen(aSubject, aMode) {
  // Note: mode is fixed to 'pull'
  PL.listen(aSubject);
}

// Shorthand: Join the pushlet server and start listening immediately
function p_join_listen(aSubject) {
  PL.joinListen(aSubject);
}

// Leave the pushlet server
function p_leave() {
  PL.leave();
}

// Send heartbeat event; callback is onHeartbeatAck()
function p_heartbeat() {
  PL.heartbeat();
}

// Publish to a subject
function p_publish(aSubject, nvPairs) {
  var args = p_publish.arguments;
  
  // Put the arguments' name/value pairs in the URI
  var query = '';
  var amp = '';
  for (var i = 1; i < args.length; i++) {
    if (i > 1) {
      amp = '&';
    }
    query = query + amp + args[i] + '=' + args[++i];
  }
  PL.publish(aSubject, query);
}

// Subscribe to a subject with optional label
function p_subscribe(aSubject, aLabel) {
  PL.subscribe(aSubject, aLabel);
}

// Unsubscribe from a subject
function p_unsubscribe(aSid) {
  PL.unsubscribe(aSid);
}

/**********************************************************************
 END - Public application functions (LEFT HERE FOR FORWARD COMPAT)
 ***********************************************************************/

// Initialize when page completely loaded
//PL._addEvent(window, 'load', PL._init, false);




function p_load(){
//alert("init.....");
PL._init();
}


